﻿using Manon.Repository.Collections;
using Manon.Repository.Extensions;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Manon.Repository
{
    public static class IQueryablePageListExtensions
    {



        #region  查询所有
        public static IEnumerable<TEntity> GetAll<TEntity>(this IOrderedQueryable<TEntity> source) => source.ToList();

        public static Task<List<TEntity>> GetAllAsync<TEntity>(this IOrderedQueryable<TEntity> source) => source.ToListAsync();



        public static IEnumerable<TEntity> GetAllByWhere<TEntity>(this IOrderedQueryable<TEntity> source, Expression<Func<TEntity, bool>> where)
        {
            return source.Where(where).ToList();
        }

        public static Task<List<TEntity>> GetAllByWhereAsync<TEntity>(this IOrderedQueryable<TEntity> source, Expression<Func<TEntity, bool>> where)
        {
            return source.Where(where).ToListAsync();
        }


        #endregion





        #region 查询单条记录 

        public static TEntity GetFirstOrDefault<TEntity>(this IOrderedQueryable<TEntity> source)
        {
            return source.FirstOrDefault();
        }

        public static Task<TEntity> GetFirstOrDefaultAsync<TEntity>(this IOrderedQueryable<TEntity> source, CancellationToken cancellationToken = default)
        {
            return source.FirstOrDefaultAsync();
        }

        public static Task<TEntity> GetFirstOrDefaultAsync<TEntity>(this IOrderedQueryable<TEntity> source, Expression<Func<TEntity, bool>> predicate, CancellationToken cancellationToken = default)
        {
            return source.Where(predicate).FirstOrDefaultAsync();
        }


        #endregion

        #region Find查询 

        public static ValueTask<TEntity> FindAsync<TEntity>(this IOrderedQueryable<TEntity> source, params object[] keyValues)
        {
            return source.FindAsync(keyValues);
        }


        #endregion



        /// <summary>
        /// 排序 正序
        /// </summary>
        /// <param name="source"></param>
        /// <param name="orderBy"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> ThenBy<TEntity>(this IOrderedQueryable<TEntity> source, Expression<Func<TEntity, bool>> orderBy)
        {
            return source.ThenBy(orderBy);
        }

        /// <summary>
        /// 排序 倒序
        /// </summary>
        /// <param name="source"></param>
        /// <param name="orderBy"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> ThenByDescending<TEntity>(this IOrderedQueryable<TEntity> source, Expression<Func<TEntity, bool>> orderBy)
        {
            return source.ThenByDescending(orderBy);
        }

        /// <summary>
        /// 在数据中取得固定页的数据（异步操作）
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页大小</param>
        /// <param name="indexFrom">起始页</param>
        /// <param name="cancellationToken">异步观察参数</param>
        /// <returns></returns>
        public static async Task<PagedList<T>> ToPagedListAsync<T>(this IQueryable<T> source, BasePageInput Input, CancellationToken cancellationToken = default(CancellationToken))
        {
            SetBasePageInput(Input);

            if (Input.IndexFrom > Input.PageIndex)
            {
                throw new ArgumentException($"indexFrom: {Input.IndexFrom} > pageIndex: {Input.PageIndex}, must indexFrom <= pageIndex");
            }

            var count = await source.CountAsync(cancellationToken).ConfigureAwait(false);
            var items = await source.OrderByCondition(Input.order).Skip((Input.PageIndex - Input.IndexFrom) * Input.PageSize)
                                    .Take(Input.PageSize).ToListAsync(cancellationToken).ConfigureAwait(false);

            var pagedList = new PagedList<T>()
            {
                PageIndex = Input.PageIndex,
                PageSize = Input.PageSize,
                IndexFrom = Input.IndexFrom,
                TotalCount = count,
                Items = items,
                TotalPages = (int)Math.Ceiling(count / (double)Input.PageSize)
            };

            return pagedList;
        }






        /// <summary>
        /// 在数据中取得固定页的数据（异步操作）
        /// </summary>
        /// <typeparam name="T">数据类型</typeparam>
        /// <param name="source">数据源</param>
        /// <param name="pageIndex">当前页</param>
        /// <param name="pageSize">页大小</param>
        /// <param name="indexFrom">起始页</param>
        /// <param name="cancellationToken">异步观察参数</param>
        /// <returns></returns>
        public static async Task<PagedList<TResult>> ToPagedListAsync<TResult, TSource>(this IQueryable<TSource> source, BasePageInput Input, CancellationToken cancellationToken = default(CancellationToken))
        {
            SetBasePageInput(Input);

            if (Input.IndexFrom > Input.PageIndex)
            {
                throw new ArgumentException($"indexFrom: {Input.IndexFrom} > pageIndex: {Input.PageIndex}, must indexFrom <= pageIndex");
            }

            var count = await source.CountAsync(cancellationToken).ConfigureAwait(false);
            var items = await source.OrderByCondition(Input.order).Skip((Input.PageIndex - Input.IndexFrom) * Input.PageSize)
                                    .Take(Input.PageSize).ToListAsync(cancellationToken).ConfigureAwait(false);
            IEnumerable<TResult>? itemResult = items == null ? null : ObjectConvertExtensions<TSource, TResult>.ConvertTo(items);

            var pagedList = new PagedList<TResult>()
            {
                PageIndex = Input.PageIndex,
                PageSize = Input.PageSize,
                IndexFrom = Input.IndexFrom,
                TotalCount = count,
                Items = itemResult,
                TotalPages = (int)Math.Ceiling(count / (double)Input.PageSize)
            };

            return pagedList;
        }



        /// <summary>
        /// 初始化实例
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">The index of the page.</param>
        /// <param name="pageSize">The size of the page.</param>
        /// <param name="indexFrom">The index from.</param>
        public static PagedList<TSource> ToPagedList<TSource>(this IQueryable<TSource> source, BasePageInput Input)
        {
            SetBasePageInput(Input);

            if (Input.IndexFrom > Input.PageIndex)
            {
                throw new ArgumentException($"起始页: {Input.IndexFrom} > 当前页: {Input.PageIndex},起始页必须小于等于当前页");
            }

            ////公共筛选过滤
            //Expression<Func<TSource, bool>> whereExpression = Input.Filters.ToConditionExpression<TSource>();
            //source = source.Where(whereExpression);

            var count = source.Count();
            var items = source.OrderByCondition(Input.order).Skip((Input.PageIndex - Input.IndexFrom) * Input.PageSize)
                                    .Take(Input.PageSize).ToList();

            var pagedList = new PagedList<TSource>()
            {
                PageIndex = Input.PageIndex,
                PageSize = Input.PageSize,
                IndexFrom = Input.IndexFrom,
                TotalCount = count,
                Items = items,
                TotalPages = (int)Math.Ceiling(count / (double)Input.PageSize)
            };

            return pagedList;
        }

        private static void SetBasePageInput(BasePageInput Input)
        {
            if (Input.PageIndex == 0) Input.PageIndex = 1;
            if (Input.PageSize == 0) Input.PageSize = 20;
            if (Input.IndexFrom == 0) Input.IndexFrom = 1;
            if (Input.PageSize > 1000) Input.PageSize = 1000;
            if (Input.PageIndex > 100000000) Input.PageIndex = 100000000;


            if (Input.order == null)
            {
                Input.order = new List<QueryOrderInput>() {
                      new QueryOrderInput(){
                            FieldName="Id",
                            Order="Desc"
                      }
                 };
            }
        }


        /// <summary>
        /// 初始化实例
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="pageIndex">The index of the page.</param>
        /// <param name="pageSize">The size of the page.</param>
        /// <param name="indexFrom">The index from.</param>
        public static PagedList<TResult> ToPagedList<TResult, TSource>(this IQueryable<TSource> source, BasePageInput Input)
        {
            SetBasePageInput(Input);

            if (Input.IndexFrom > Input.PageIndex)
            {
                throw new ArgumentException($"起始页: {Input.IndexFrom} > 当前页: {Input.PageIndex},起始页必须小于等于当前页");
            }


            var count = source.Count();
            var items = source.OrderByCondition(Input.order).Skip((Input.PageIndex - Input.IndexFrom) * Input.PageSize)
                                    .Take(Input.PageSize).ToList();
            IEnumerable<TResult>? itemResult = items == null ? null : ObjectConvertExtensions<TSource, TResult>.ConvertTo(items);
            var pagedList = new PagedList<TResult>()
            {
                PageIndex = Input.PageIndex,
                PageSize = Input.PageSize,
                IndexFrom = Input.IndexFrom,
                TotalCount = count,
                Items = itemResult,
                TotalPages = (int)Math.Ceiling(count / (double)Input.PageSize)
            };

            return pagedList;
        }
        public static IQueryable<T> OrderByCondition<T>(this IQueryable<T> query, IEnumerable<QueryOrderInput> orderConditions)
        {
            if (orderConditions == null || orderConditions.Count() == 0)
            {
                orderConditions = new List<QueryOrderInput>() {
                     new QueryOrderInput()
                     {
                          FieldName="Id",
                          Order="Desc"
                     }
                };
            }

            bool hasOrder = false;
            IOrderedQueryable<T> result = null;
            foreach (var orderinfo in orderConditions)
            {
                var t = typeof(T);
                var propertyInfo = t.GetProperty(orderinfo.FieldName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                if (propertyInfo == null) continue;

                string orderBy = orderinfo.FieldName;


                if (string.Equals(orderinfo.Order, OrderSequence.Desc.ToString(), StringComparison.CurrentCultureIgnoreCase))
                {
                    result = hasOrder ? result.ThenByDescending(orderBy) : query.OrderByDescending(orderBy);
                }
                else
                {
                    result = hasOrder ? result.ThenBy(orderBy) : query.OrderBy(orderBy);
                }
                hasOrder = true;
            }
            return result;
        }

        public static IOrderedQueryable<TEntity> OrderBy<TEntity>(this IQueryable<TEntity> source, string fieldName)
        {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "OrderBy", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> OrderByDescending<TEntity>(this IQueryable<TEntity> source, string fieldName)
        {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "OrderByDescending", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> ThenBy<TEntity>(this IOrderedQueryable<TEntity> source, string fieldName)
        {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "ThenBy", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }

        public static IOrderedQueryable<TEntity> ThenByDescending<TEntity>(this IOrderedQueryable<TEntity> source, string fieldName)
        {
            MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "ThenByDescending", fieldName);
            return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
        }
        private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, String fieldName)
        {
            Type type = typeof(TEntity);
            Type selectorResultType;
            LambdaExpression selector = GenerateSelector<TEntity>(fieldName, out selectorResultType);
            MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName, new Type[] { type, selectorResultType }, source.Expression, Expression.Quote(selector));
            return resultExp;
        }

        private static LambdaExpression GenerateSelector<TEntity>(String propertyName, out Type resultType)
        {
            // Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).            
            var parameter = Expression.Parameter(typeof(TEntity), "Entity");
            //  create the selector part, but support child properties            
            PropertyInfo property;
            Expression propertyAccess;
            if (propertyName.Contains('.'))
            {
                // support to be sorted on child fields.                
                String[] childProperties = propertyName.Split('.');
                property = typeof(TEntity).GetProperty(childProperties[0], BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);
                for (int i = 1; i < childProperties.Length; i++)
                {
                    property = property.PropertyType.GetProperty(childProperties[i], BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                    propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
                }
            }
            else
            {
                property = typeof(TEntity).GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
                propertyAccess = Expression.MakeMemberAccess(parameter, property);
            }
            resultType = property.PropertyType;
            // Create the order by expression.            
            return Expression.Lambda(propertyAccess, parameter);
        }

    }
}
